package mychi

import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"sync"
)

var _ Router = &Mux{}

type Mux struct {
	handler                 http.Handler
	tree                    *node
	methodNotAllowedHandler http.HandlerFunc
	parent                  *Mux
	pool                    *sync.Pool
	notFoundHandler         http.HandlerFunc
	middlewares             []func(http.Handler) http.Handler
	inline                  bool
}

func NewMux() *Mux {
	mux := &Mux{
		tree: &node{},
		pool: &sync.Pool{},
	}
	mux.pool.New = func() any {
		return NewRouteContext()
	}
	return mux
}

func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if mx.handler == nil {
		mx.NotFoundHandler().ServeHTTP(w, r)
		return
	}
	rctx, _ := r.Context().Value(RouteCtxKey).(*Context)
	if rctx != nil {
		mx.handler.ServeHTTP(w, r)
		return
	}
	rctx = mx.pool.Get().(*Context)
	rctx.Reset()
	rctx.Routes = mx
	rctx.parentCtx = r.Context()

	r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))
	mx.handler.ServeHTTP(w, r)
	mx.pool.Put(rctx)
}

func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) {
	if mx.handler != nil {
		panic("all middlewares must be set before handler")
	}
	mx.middlewares = append(mx.middlewares, middlewares...)
}

func (mx *Mux) Handle(pattern string, handler http.Handler) {
	if i := strings.IndexAny(pattern, " \t"); i >= 0 {
		method, rest := pattern[:i], strings.TrimLeft(pattern[i+1:], " \t")
		mx.Method(method, rest, handler)
		return
	}

	mx.handle(mALL, pattern, handler)
}

func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) {
	mx.Handle(pattern, handlerFn)
}

func (mx *Mux) Method(method, pattern string, handler http.Handler) {
	m, ok := methodMap[strings.ToUpper(method)]
	if !ok {
		panic(fmt.Sprintf("'%s' http method is not supported.", method))
	}
	mx.handle(m, pattern, handler)
}

func (mx *Mux) MethodFunc(method, pattern string, handlerFn http.HandlerFunc) {
	mx.Method(method, pattern, handlerFn)
}

func (mx *Mux) Connect(pattern string, handlerFn http.HandlerFunc) {
	mx.handle(mCONNECT, pattern, handlerFn)
}

func (mx *Mux) Delete(pattern string, handlerFn http.HandlerFunc) {
	mx.handle(mDELETE, pattern, handlerFn)
}

func (mx *Mux) Get(pattern string, handlerFn http.HandlerFunc) {
	mx.handle(mGET, pattern, handlerFn)
}

func (mx *Mux) Head(pattern string, handlerFn http.HandlerFunc) {
	mx.handle(mHEAD, pattern, handlerFn)
}

func (mx *Mux) Options(pattern string, handlerFn http.HandlerFunc) {
	mx.handle(mOPTIONS, pattern, handlerFn)
}

func (mx *Mux) Patch(pattern string, handlerFn http.HandlerFunc) {
	mx.handle(mPATCH, pattern, handlerFn)
}

func (mx *Mux) Post(pattern string, handlerFn http.HandlerFunc) {
	mx.handle(mPOST, pattern, handlerFn)
}

func (mx *Mux) Put(pattern string, handlerFn http.HandlerFunc) {
	mx.handle(mPUT, pattern, handlerFn)
}

func (mx *Mux) Trace(pattern string, handlerFn http.HandlerFunc) {
	mx.handle(mTRACE, pattern, handlerFn)
}

func (mx *Mux) NotFound(handlerFn http.HandlerFunc) {
	m := mx
	hFn := handlerFn
	if mx.inline && mx.parent != nil {
		m = mx.parent
		hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP
	}
	m.notFoundHandler = hFn
	m.updateSubRoutes(func(subMux *Mux) {
		if subMux.notFoundHandler == nil {
			subMux.NotFound(hFn)
		}
	})
}

func (mx *Mux) NotFoundHandler() http.HandlerFunc {
	if mx.notFoundHandler != nil {
		return mx.notFoundHandler
	}
	return http.NotFound
}

func (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) {
	m := mx
	hFn := handlerFn
	if mx.inline && mx.parent != nil {
		m = mx.parent
		hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP
	}
	m.methodNotAllowedHandler = hFn
	m.updateSubRoutes(func(subMux *Mux) {
		if subMux.methodNotAllowedHandler == nil {
			subMux.MethodNotAllowed(hFn)
		}
	})
}

func (mx *Mux) MethodNotAllowedHandler(methodsAllowed ...methodTyp) http.HandlerFunc {
	if mx.methodNotAllowedHandler != nil {
		return mx.methodNotAllowedHandler
	}
	return methodNotAllowedHandler(methodsAllowed...)
}

func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {
	if !mx.inline && mx.handler == nil {
		mx.updateRouteHandler()
	}

	var mws Middlewares
	if mx.inline {
		mws = make(Middlewares, len(mx.middlewares))
		copy(mws, mx.middlewares)
	}
	mws = append(mws, middlewares...)

	im := &Mux{
		pool:                    mx.pool,
		inline:                  true,
		parent:                  mx,
		tree:                    mx.tree,
		middlewares:             mws,
		notFoundHandler:         mx.notFoundHandler,
		methodNotAllowedHandler: mx.methodNotAllowedHandler,
	}
	return im
}

func (mw *Mux) Group(fn func(r Router)) Router {
	im := mw.With()
	if fn != nil {
		fn(im)
	}
	return im
}

func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
	if fn == nil {
		panic(fmt.Sprintf("'%s' fn must not be nil.", pattern))
	}
	subRouter := NewRouter()
	fn(subRouter)
	mx.Mount(pattern, subRouter)
	return subRouter
}

func (mx *Mux) Mount(pattern string, handler http.Handler) {
	if handler == nil {
		panic(fmt.Sprintf("'%s' handler must not be nil.", pattern))
	}

	if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") {
		panic(fmt.Sprintf("'%s' pattern is not allowed.", pattern))
	}

	subr, ok := handler.(*Mux)
	if ok && subr.notFoundHandler == nil && mx.notFoundHandler != nil {
		subr.NotFound(mx.notFoundHandler)
	}
	if ok && subr.methodNotAllowedHandler == nil && mx.methodNotAllowedHandler != nil {
		subr.MethodNotAllowed(mx.methodNotAllowedHandler)
	}

	mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rctx := RouteContext(r.Context())
		rctx.RoutePath = mx.nextRoutePath(rctx)

		n := len(rctx.URLParams.Keys) - 1
		if n >= 0 && rctx.URLParams.Keys[n] == "*" && len(rctx.URLParams.Values) > n {
			rctx.URLParams.Values[n] = ""
		}
		handler.ServeHTTP(w, r)
	})

	method := mALL
	subroutes, _ := handler.(Routes)
	if subroutes != nil {
		method |= mSTUB
	}
	n := mx.handle(method, pattern+"*", mountHandler)

	if subroutes != nil {
		n.subroutes = subroutes
	}
}

func (mx *Mux) Routes() []Route {
	return mx.tree.routes()
}

func (mx *Mux) Middlewares() Middlewares {
	return mx.middlewares
}

func (mx *Mux) Match(rctx *Context, method, path string) bool {
	return mx.Find(rctx, method, path) != ""
}

func (mx *Mux) Find(rctx *Context, method, path string) string {
	m, ok := methodMap[method]
	if !ok {
		return ""
	}

	node, _, _ := mx.tree.FindRoute(rctx, m, path)
	pattern := rctx.routePattern
	if node != nil {
		if node.subroutes == nil {
			e := node.endpoints[m]
			return e.pattern
		}
		rctx.RoutePath = mx.nextRoutePath(rctx)
		subPattern := node.subroutes.Find(rctx, method, rctx.RoutePath)
		if subPattern == "" {
			return ""
		}
		pattern = strings.TrimSuffix(pattern, "/*")
		pattern += subPattern
	}
	return pattern
}

func (mx *Mux) nextRoutePath(rctx *Context) string {
	routePath := "/"
	nx := len(rctx.routeParams.Keys) - 1
	if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx {
		routePath = "/" + rctx.routeParams.Values[nx]
	}
	return routePath
}

func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node {
	if len(pattern) == 0 || pattern[0] != '/' {
		panic(fmt.Sprintf("'%s' pattern must start with '/'.", pattern))
	}

	if !mx.inline && mx.handler == nil {
		mx.updateRouteHandler()
	}
	var h http.Handler
	if mx.inline {
		mx.handler = http.HandlerFunc(mx.routeHTTP)
		h = Chain(mx.middlewares...).Handler(handler)
	} else {
		h = handler
	}
	return mx.tree.InsertRoute(method, pattern, h)
}

func (mx *Mux) updateRouteHandler() {
	mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP))
}

func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) {
	for _, r := range mx.tree.routes() {
		subMux, ok := r.SubRoutes.(*Mux)
		if !ok {
			continue
		}
		fn(subMux)
	}
}

func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) {
	rctx := r.Context().Value(RouteCtxKey).(*Context)
	routePath := rctx.RoutePath
	if routePath == "" {
		if r.URL.RawPath == "" {
			if r.URL.RawPath != "" {
				routePath = r.URL.RawPath
			} else {
				routePath = r.URL.Path
			}
			if routePath == "" {
				routePath = "/"
			}
		}
	}

	if rctx.RouteMethod == "" {
		rctx.RouteMethod = r.Method
	}
	method, ok := methodMap[rctx.RouteMethod]
	if !ok {
		mx.MethodNotAllowedHandler().ServeHTTP(w, r)
		return
	}

	if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil {
		for i, key := range rctx.URLParams.Keys {
			value := rctx.URLParams.Values[i]
			r.SetPathValue(key, value)
		}
		if supportsPattern {
			setPattern(rctx, r)
		}
		h.ServeHTTP(w, r)
		return
	}
	if rctx.methodNotAllowed {
		mx.MethodNotAllowedHandler(rctx.methodsAllowed...).ServeHTTP(w, r)
	} else {
		mx.NotFoundHandler().ServeHTTP(w, r)
	}
}

func methodNotAllowedHandler(methodsAllowed ...methodTyp) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		for _, m := range methodsAllowed {
			w.Header().Add("Allow", reverseMethodMap[m])
		}
		w.WriteHeader(405)
		w.Write(nil)
	}
}
